package com.xwy.usercenter.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import com.xwy.usercenter.common.BaseResponse;
import com.xwy.usercenter.common.ErrorCode;
import com.xwy.usercenter.common.ResultUtils;
import com.xwy.usercenter.exception.BusinessException;
import com.xwy.usercenter.mapper.TeamMapper;
import com.xwy.usercenter.model.Team;
import com.xwy.usercenter.model.User;
import com.xwy.usercenter.model.UserTeam;
import com.xwy.usercenter.model.dto.TeamQuery;
import com.xwy.usercenter.model.enums.TeamStatusEnum;
import com.xwy.usercenter.model.request.TeamJoinRequest;
import com.xwy.usercenter.model.request.TeamQuitRequest;
import com.xwy.usercenter.model.request.TeamUpdateRequest;
import com.xwy.usercenter.model.vo.TeamUserVO;
import com.xwy.usercenter.model.vo.UserVO;
import com.xwy.usercenter.service.TeamService;
import com.xwy.usercenter.service.UserService;
import com.xwy.usercenter.service.UserTeamService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.formula.functions.T;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static com.xwy.usercenter.utils.RedisConstants.*;

/**
 *
 */
@Service
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
    implements TeamService {

    @Resource
    private UserTeamService userTeamService;

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;

    @Override
    @Transactional(rollbackFor = Exception.class)//TODO
    public Long addTeam(Team team, User loginUser) {
        if (team == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //3. 校验信息
        //3.1. 队伍人数 > 1 且 <= 20
        Integer teamNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
        if (teamNum < 1 || teamNum > 20){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数不满足要求");
        }
        //3.2. 队伍标题 <= 20
        String teamName = team.getName();
        if (StringUtils.isBlank(teamName) ||teamName.length() > 20){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题不满足要求");
        }
        //3.3. 描述 <= 512
        String description = team.getDescription();
        //队伍描述可以是空的，如果不是空的情况下，长度不符合规则抛异常
        if (StringUtils.isNotBlank(description) && description.length() > 512){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述不满足要求");
        }
        //3.4. status 是否公开（int）不传默认为 0（公开）
        int teamStatus = Optional.ofNullable(team.getStatus()).orElse(0);
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(teamStatus);
        //如果状态枚举中，不存在该状态，则抛出异常
        if (statusEnum == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态不满足要求");
        }
        //3.5. 如果 status 是加密状态，一定要有密码，且密码 <= 32
        if (TeamStatusEnum.SECRET.equals(statusEnum)){
            String password = team.getPassword();
            if (StringUtils.isBlank(password) || password.length() > 32){
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍密码不满足要求");
            }
        }
        //3.6. 超时时间 > 当前时间
        //时间比较！！！
        Date expireTime = team.getExpireTime();
        if (new Date().after(expireTime)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍时间不满足要求");
        }
        //3.7. 校验用户最多创建 5 个队伍
        Long userId = loginUser.getId();
        LambdaQueryWrapper<Team> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Team::getUserId, userId);
        int count = count(queryWrapper);
        if (count >= 5){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍不能超过5个");
        }
        //4. 插入队伍信息到队伍表
        team.setId(null);
        team.setUserId(userId);
        boolean result = save(team);
        if (!result){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "插入失败");
        }
        //5. 插入用户  => 队伍关系到关系表
        UserTeam userTeam = new UserTeam();
        userTeam.setUserId(userId);
        userTeam.setTeamId(team.getId());
        userTeam.setJoinTime(new Date());
        result = userTeamService.save(userTeam);
        if (!result){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "插入失败");
        }
        return team.getId();
    }

    @Override
    public List<TeamUserVO> listTeam(TeamQuery teamQuery, Boolean isAdmin) {
        LambdaQueryWrapper<Team> queryWrapper = new LambdaQueryWrapper<>();
        if (teamQuery != null){
            //根据id查询
            Long id = teamQuery.getId();
            if (id != null && id > 0){
                queryWrapper.eq(Team::getId, id);
            }
            List<Long> idList = teamQuery.getIdList();
            if (CollectionUtils.isNotEmpty(idList)) {
                queryWrapper.in(Team::getId, idList);
            }
            //根据队伍查询
            String name = teamQuery.getName();
//            if (StringUtils.isNotBlank(name)){
//                queryWrapper.like(Team::getName, name);
//            }
            //根据描述查询
            String description = teamQuery.getDescription();
//            if (StringUtils.isNotBlank(description)){
//                queryWrapper.like(Team::getDescription, description);
//            }
            //输入字段模糊匹配队伍名或者描述
            String searchText = teamQuery.getSearchText();
            if (StringUtils.isNotBlank(searchText)){
                queryWrapper.and(qw -> qw.like(Team::getName, name).or().like(Team::getDescription,description));
            }

            //根据队伍最大人数查询
            Integer maxNum = teamQuery.getMaxNum();
            if (maxNum != null && maxNum > 0){
                queryWrapper.eq(Team::getMaxNum, maxNum);
            }
            //根据创建者的id查询
            Long userId = teamQuery.getUserId();
            if (userId != null && userId > 0){
                queryWrapper.eq(Team::getUserId, userId);
            }
            //根据队伍状态进行查询(只有管理员才能查看加密还有非公开的房间)
            Integer status = teamQuery.getStatus();
            TeamStatusEnum enumStatus = TeamStatusEnum.getEnumByValue(status);
            if (enumStatus == null){
                //如果状态为空默认是公开的状态
                enumStatus = TeamStatusEnum.PUBLIC;
            }
            //如果非管理员，并且还不是公开的队伍，那么查不出来 TODO 逻辑优化
            if (!isAdmin && enumStatus.equals(TeamStatusEnum.PRIVATE)){
                throw new BusinessException(ErrorCode.NO_AUTH);
            }
            queryWrapper.eq(Team::getStatus, status);
        }
        // 不展示已过期的队伍
        // expireTime is null or expireTime > now()
        queryWrapper.and(qw -> qw.gt(Team::getExpireTime, new Date()).or().isNull(Team::getExpireTime));

        //查询出符合查询条件的队伍信息
        List<Team> teamList = list(queryWrapper);
        //如果为空，返回空集合
        if (CollectionUtil.isEmpty(teamList)){
            return new ArrayList<>();
        }

        //队伍及创建人信息列表
        List<TeamUserVO> teamUserVOList = new ArrayList<>();

        //关联查询创建队伍的人的用户信息(TODO 这边需要再琢磨琢磨，sql版)
        for (Team team : teamList) {
            Long userId = team.getUserId();
            if (userId == null){
                continue;
            }
            TeamUserVO teamUserVO = new TeamUserVO();
            User user = userService.getById(userId);
            if (user != null){
                UserVO userVO = new UserVO();
                //对用户信息进行脱敏
                BeanUtil.copyProperties(user, userVO);
                //将用户信息设置进队伍用户信息VO
                teamUserVO.setCreateUser(userVO);
            }
            BeanUtil.copyProperties(team, teamUserVO);
            teamUserVOList.add(teamUserVO);
        }
        return teamUserVOList;
    }

    @Override
    public Boolean updateTeam(TeamUpdateRequest teamUpdateRequest, User loginUser) {
        //1. 判断请求参数是否为空
        if (teamUpdateRequest == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //2. 查询队伍是否存在
        Long teamId = teamUpdateRequest.getId();
        Team oldTeam = this.getTeam(teamId);
        //3. 只有管理员或者队伍的创建者可以修改
        if (!userService.isAdmin(loginUser) && loginUser.getId() != oldTeam.getUserId()){
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        //4. 如果用户传入的新值和老值一致，就不用 update 了（可自行实现，降低数据库使用次数）
        //5. 如果队伍状态改为加密，必须要有密码
        Team updataTeam = new Team();
        Integer status = oldTeam.getStatus();
        TeamStatusEnum enumStatus = TeamStatusEnum.getEnumByValue(status);
        if (enumStatus.equals(TeamStatusEnum.SECRET)){
            if (StringUtils.isBlank(teamUpdateRequest.getPassword())) {
                throw new BusinessException(ErrorCode.NULL_ERROR, "加密房间必须设置密码");
            }
        }
        //6. 更新成功
        BeanUtil.copyProperties(teamUpdateRequest, updataTeam);
        boolean result = updateById(updataTeam);
        return result;
    }

    @Override
    public Boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser) {
        if (teamJoinRequest == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        Long userId = loginUser.getId();
        // TODO 目前该分布式锁的锁粒度较高，可以修改成对不同用户可以同时加入某个队伍
        RLock lock = redissonClient.getLock(CPMATCH_REPEJOINTEAM_LOCK);
        try {
            while (true){
                if (lock.tryLock(0, -1L, TimeUnit.MILLISECONDS)){
                    //1. 用户最多加入 5 个队伍
                    LambdaQueryWrapper<UserTeam> queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(UserTeam::getUserId, userId);
                    int count = userTeamService.count(queryWrapper);
                    if (count >= 5){
                        throw new BusinessException(ErrorCode.SYSTEM_ERROR, "用户最多创建或者加入5个队伍");
                    }
                    // 2. 队伍必须存在，只能加入未满、未过期的队伍
                    Long teamId = teamJoinRequest.getTeamId();
                    Team team = this.getTeam(teamId);
                    int teamMemCount = getTeamMemCount(teamId);
                    if (teamMemCount >= team.getMaxNum()){
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数已满");
                    }
                    Date expireTime = team.getExpireTime();
                    if (expireTime != null && new Date().after(expireTime)){
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期");
                    }
                    //3. 不能加入自己的队伍，不能重复加入已加入的队伍（幂等性）
                    if (userId.equals(team.getUserId())){
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "不能加入自己创建的队伍");
                    }
                    queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(UserTeam::getUserId, userId)
                            .eq(UserTeam::getTeamId, teamId);
                    count = userTeamService.count(queryWrapper);
                    if (count > 0){
                        throw new BusinessException(ErrorCode.SYSTEM_ERROR, "已经加入该队伍，不可重复加入");
                    }
                    //4. 禁止加入私有的队伍
                    Integer status = team.getStatus();
                    TeamStatusEnum enumStatus = TeamStatusEnum.getEnumByValue(status);
                    if (enumStatus.equals(TeamStatusEnum.PRIVATE)){
                        throw new BusinessException(ErrorCode.NULL_ERROR, "禁止加入私有的队伍");
                    }
                    //5. 如果加入的队伍是加密的，必须密码匹配才可以
                    if (enumStatus.equals(TeamStatusEnum.SECRET)){
                        String password = teamJoinRequest.getPassword();
                        if (StringUtils.isBlank(password) && !team.getPassword().equals(password)){
                            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
                        }
                    }
                    //6. 新增队伍 - 用户关联信息
                    UserTeam userTeam = new UserTeam();
                    userTeam.setUserId(userId);
                    userTeam.setTeamId(teamId);
                    userTeam.setJoinTime(new Date());
                    boolean result = userTeamService.save(userTeam);
                    return result;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        } finally {
            //Checks if this lock is held by the current thread
            //判断当前锁是否是当前的线程拿着的
            if (lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser) {
        //1. 校验请求参数
        if (teamQuitRequest == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //2. 校验队伍是否存在
        Long teamId = teamQuitRequest.getTeamId();
        Team team = this.getTeam(teamId);
        //3. 校验我是否已加入队伍
        Long userId = loginUser.getId();
        LambdaQueryWrapper<UserTeam> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserTeam::getUserId, userId).eq(UserTeam::getTeamId, teamId);
        int count = userTeamService.count(queryWrapper);
        if (count == 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入此队伍");
        }
        //4.1如果队伍只剩一人，队伍解散
        count = getTeamMemCount(teamId);
        if (count == 1){
            //删队伍
            removeById(teamId);
        }else {
            //4.2还有其他人
            if (userId.equals(team.getUserId())){
                //4.2.1如果是队长退出队伍，权限转移给第二早加入的用户 —— 先来后到
                //> 只用取 id 最小的 2 条数据
                LambdaQueryWrapper<UserTeam> userTeamQueryWrapper = new LambdaQueryWrapper<>();
                userTeamQueryWrapper.eq(UserTeam::getTeamId, teamId);
                userTeamQueryWrapper.last("order by id asc limit 2");
                List<UserTeam> userTeamList = userTeamService.list(userTeamQueryWrapper);
                if (CollectionUtils.isEmpty(userTeamList) || userTeamList.size() <= 1) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR);
                }
                UserTeam nextUserTeam = userTeamList.get(1);
                Long nextTeamLeaderId = nextUserTeam.getUserId();
                // 更新当前队伍的队长
                Team updateTeam = new Team();
                updateTeam.setId(teamId);
                updateTeam.setUserId(nextTeamLeaderId);
                boolean result = this.updateById(updateTeam);
                if (!result) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队伍队长失败");
                }
            }
        }
        //4.4.2非队长，自己退出队伍
        //删队伍用户关系
        return userTeamService.remove(queryWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeTeam(Long teamId, User loginUser) {
        //1.判断队伍是否存在
        Team team = this.getTeam(teamId);
        //2.判断是否为当前队伍的队长
        Long userId = loginUser.getId();
        if (!userId.equals(team.getUserId())){
         throw new BusinessException(ErrorCode.NO_AUTH);
        }
        //3.移除所有加入队伍的关联信息
        LambdaQueryWrapper<UserTeam> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserTeam::getTeamId, teamId);
        boolean result = userTeamService.remove(queryWrapper);
        if (!result){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除队伍关联信息失败");
        }
        //4.删除队伍
        return removeById(teamId);
    }

    /**
     * 获取队伍人数
     * @param teamId
     * @return
     */
    private int getTeamMemCount(Long teamId) {
        LambdaQueryWrapper<UserTeam> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserTeam::getTeamId, teamId);
        return userTeamService.count(queryWrapper);
    }

    /**
     * 校验队伍是否存在
     * @param teamId
     * @return
     */
    private Team getTeam(Long teamId){
        //小于等于0可以一定上减少缓存穿透，防止对方拿负数去攻击
        if (teamId == null || teamId <= 0){
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        Team team = getById(teamId);
        if (team == null){
            throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
        }
        return team;
    }
}

//锁的应该是个对象，如果userId是long型，则应该转成字符串，并用String的intern()方法，
//调用该方法时，如果字符串常量池中已经存在一个等于此String对象的字符串，
//就直接从常量池中返回这个字符串对象的引用
//如果调用该方法的这个字符串对象，目前字符串常量池中还并不存在这个字符串对象，
//也就是说这个字符串对象是首次出现的，那么就新建并添加这个字符串对象到字符串常量池中，
//并返回新建的字符串对象的引用
//如果不使用intern()方法，则每次锁都是new一个新对象，那么即便是同一个userId，那么对象也
//是不一样的，我们是需要一致对象的
//        synchronized (String.valueOf(userId).intern()){
//
//而我这里的userId使用的是Long型的，它是一个对象，所以可以直接使用
//synchronized (userId){
//
//        }



